from distutils.errors import DistutilsError

import os
from pprint import PrettyPrinter
import sys
import time

from dbmigrate.command import BaseMigrationCommand
from dbmigrate.migration import datasources

class make_migration(BaseMigrationCommand):

    description = "create a new migration"
    command_consumes_arguments = True    
    
    user_options = [
                    ('package=', 'p', "the package containing the migration scripts"
                     "(default: migrations)"),
                    ('name=', 'n', "the migration name"),
                    ('index=', 'i', "the migration revision number"),
                    ('revision=', 'r', "the migration revision number"),
#                    ('verbose', 'v', "produce verbose output"),
                    ]
#    boolean_options = ['verbose']
    
    def initialize_options(self):
        BaseMigrationCommand.initialize_options(self)
        self.name = None
        self.index = None
        self.revision = None
        self.args = None
        
    def finalize_options(self):
        self.process_args()
        BaseMigrationCommand.finalize_options(self)
        self.assert_name_defined()
        self.assert_only_one_of_index_or_revision()
        self.set_migration_index()                
        
    def run(self):
        self.make_packages_if_needed(self.package_dir, self.package_parts(self.package))
        self.make_migration()
        
    def make_migration(self):
        filename = self.migration_path(self.package_dir, self.package, self.index, self.name)
        if not self.is_creating_initial_migration():
            self.create_initial_migration_if_new_installation()
        self.msg("writing the migration file %s" % filename)
        self.assert_migration_does_not_exist(filename)
        self.write_file(filename, self.definition())

    def process_args(self):
        if self.args:
            self.package = self.args[0]
            if len(self.args) > 1:
                self.name = self.args[1]

    def assert_name_defined(self):
        if self.name is None:
            raise DistutilsError("You must name the migration")

    def assert_only_one_of_index_or_revision(self):
        if self.index and self.revision:
            raise DistutilsError("The index and revision options are mutually exclusive.")

    def package_parts(self, path):
        return path.split('.')
    
    def make_packages_if_needed(self, path, package_parts):
        if not package_parts:
            return
        package_dir = os.path.join(path, package_parts[0])
        self.make_package(package_dir)
        self.make_packages_if_needed(package_dir, package_parts[1:])

    def make_package(self, package_dir):
        if not os.path.exists(package_dir):
            self.make_package_directory(package_dir)
        init_file = os.path.join(package_dir, '__init__.py')
        if not os.path.exists(init_file):
            self.make_init_file(init_file)
            
    def make_package_directory(self, package_dir):
        self.msg("making package directory %s" % package_dir) 
        try:
            os.mkdir(package_dir)
        except IOError:
            raise DistutilsError("could not create package directory %s" % package_dir)

    def make_init_file(self, init_file):
        self.msg("making package __init__ file %s" % init_file)
        try:
            self.write_file(init_file, '')
        except IOError:
            raise DistutilsError("could not create file %s" % init_file)

    header_line_format = "# Migration template created by DBMigrate at %(year).4d/%(month).2d/%(day).2d %(hours).2d:%(minutes)s.2d:%(seconds).2d UTC."

    def is_creating_initial_migration(self):
        return int(self.index) == 1

    def definition(self):
        if self.is_creating_initial_migration():
            return self.initial_definition()
        else:
            return self.generic_definition()

    def initial_definition(self):
        migrations = {}
        for x in datasources.values():
            migrations[x.scheme] = x.initial_migration
        migrations_output = PrettyPrinter(indent=4).pformat(migrations)
        return self.migration_definition(migrations_output)

    def generic_definition(self):
        return self.migration_definition('[]')

    def migration_definition(self, formatted_migrations):
        mig = self.header_line_format % self.date_parts() + os.linesep
        mig += os.linesep
        mig += "from dbmigrate.migration import *" + os.linesep
        mig += os.linesep*2
        mig += 'migrations = ' + formatted_migrations
        return mig
    
    def assert_migration_does_not_exist(self, filename):
        if os.path.exists(filename):
            msg = "File not written: the migration file %s already exists"
            raise DistutilsError(msg % filename)
            
    def migration_path(self, package_root, package, index, name):
        return os.path.join(self.migration_dir(package_root, package), self.migration_name(index, name))
    
    def migration_dir(self, package_root, package):
        path_parts = [package_root] + self.package_parts(package)
        return os.path.join(*path_parts)
    
    def migration_name(self, index, name):
        return "%s_%s.py" % (index, name)
    
    def set_migration_index(self):
        if self.revision:
            self.index = self.revision
        if not self.index:
            self.index = self.default_index()
    
    def default_index(self):
        format = '%(year).4d%(month).2d%(day).2d%(hours).2d%(minutes).2d%(seconds).2d'
        return format % self.date_parts()
    
    def date_parts(self):
        if not hasattr(self, '_date_parts'):
            self._date_parts = self.calculate_date_parts()
        return self._date_parts
    
    def calculate_date_parts(self):
        parts = time.gmtime()
        return {'year': parts[0], 'month': parts[1], 'day': parts[2],
                 'hours': parts[3], 'minutes': parts[4], 'seconds': parts[5]}
    
    def write_file(self, filename, contents):
        f = open(filename, 'w')
        try:
            f.write(contents)
        finally:
            f.close()

    def msg(self, x):
        if self.verbose > 1:
            print x
    
    def create_initial_migration_if_new_installation(self):
        directory = self.migration_dir(self.package_dir, self.package)
        if self.has_no_python_files(directory):
            filename = self.migration_path(self.package_dir, self.package, '1', 'create_database')
            self.msg("writing initial migration file %s" % filename)
            self.write_file(filename, self.initial_definition())

    def has_no_python_files(self, directory):
        py_files = set([x for x in os.listdir(directory) if x.endswith('.py')])
        return py_files == set(['__init__.py'])


        
